package com.mtons.mblog.modules.service.impl;

import com.mtons.mblog.base.lang.Consts;
import com.mtons.mblog.base.utils.BeanMapUtils;
import com.mtons.mblog.base.utils.MarkdownUtils;
import com.mtons.mblog.base.utils.PreviewTextUtils;
import com.mtons.mblog.base.utils.ResourceLock;
import com.mtons.mblog.modules.aspect.PostStatusFilter;
import com.mtons.mblog.modules.data.PostVO;
import com.mtons.mblog.modules.data.QuestVO;
import com.mtons.mblog.modules.data.UserVO;
import com.mtons.mblog.modules.entity.*;
import com.mtons.mblog.modules.event.PostUpdateEvent;
import com.mtons.mblog.modules.repository.QuestAttributeRepository;
import com.mtons.mblog.modules.repository.QuestRepository;
import com.mtons.mblog.modules.repository.QuestResourceRepository;
import com.mtons.mblog.modules.repository.ResourceRepository;
import com.mtons.mblog.modules.service.AnswerService;
import com.mtons.mblog.modules.service.QuestService;
import com.mtons.mblog.modules.service.UserService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.persistence.criteria.Predicate;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
@Transactional
public class QuestServiceImpl implements QuestService {

    @Autowired
    private QuestRepository questRepository;
    @Autowired
    private UserService userService;
    @Autowired
    private QuestAttributeRepository questAttributeRepository;
    @Autowired
    private ResourceRepository resourceRepository;
    @Autowired
    private QuestResourceRepository questResourceRepository;
    @Autowired
    private AnswerService answerService;
    @Autowired
    private ApplicationContext applicationContext;

    private static Pattern pattern = Pattern.compile("(?<=/_signature/)(.+?)(?=\\.)");

    @Override
    @PostStatusFilter
    public Page<QuestVO> paging(Pageable pageable) {
        Page<Quest> page = questRepository.findAll((root, query, builder) -> {
            Predicate predicate = builder.conjunction();

            return predicate;
        }, pageable);
        return new PageImpl<>(toQuests(page.getContent()), pageable, page.getTotalElements());
    }

    private List<QuestVO> toQuests(List<Quest> quests) {
        HashSet<Long> uids = new HashSet<>();

        List<QuestVO> rets = quests
                .stream()
                .map(qu -> {
                    uids.add(qu.getAuthorId());
                    return BeanMapUtils.copy(qu);
                })
                .collect(Collectors.toList());

        // 加载用户信息
        buildUsers(rets, uids);

        return rets;
    }

    private void buildUsers(Collection<QuestVO> quests, Set<Long> uids) {
        Map<Long, UserVO> userMap = userService.findMapByIds(uids);
        quests.forEach(p -> p.setAuthor(userMap.get(p.getAuthorId())));
    }

    @Override
    public Page<QuestVO> paging4Admin(Pageable pageable, String title) {
        Page<Quest> page = questRepository.findAll((root, query, builder) -> {
            Predicate predicate = builder.conjunction();

            if (StringUtils.isNotBlank(title)) {
                predicate.getExpressions().add(
                        builder.like(root.get("title").as(String.class), "%" + title + "%"));
            }
            return predicate;
        }, pageable);

        return new PageImpl<>(toQuests(page.getContent()), pageable, page.getTotalElements());
    }

    @Override
    public Page<QuestVO> pagingByAuthorId(Pageable pageable, long userId) {
        Page<Quest> page = questRepository.findAllByAuthorId(pageable, userId);
        return new PageImpl<>(toQuests(page.getContent()), pageable, page.getTotalElements());
    }

    @Override
    public List<QuestVO> findLatestQuests(int maxResults) {
        return find("created", maxResults).stream().map(BeanMapUtils::copy).collect(Collectors.toList());
    }

    @PostStatusFilter
    private List<Quest> find(String orderBy, int size) {
        Pageable pageable = PageRequest.of(0, size, Sort.by(Sort.Direction.DESC, orderBy));

        Page<Quest> page = questRepository.findAll((root, query, builder) -> {
            Predicate predicate = builder.conjunction();
            return predicate;
        }, pageable);
        return page.getContent();
    }

    @Override
    public List<QuestVO> findHottestQuests(int maxResults) {
        return find("views", maxResults).stream().map(BeanMapUtils::copy).collect(Collectors.toList());
    }

    @Override
    public Map<Long, QuestVO> findMapByIds(Set<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return Collections.emptyMap();
        }

        List<Quest> list = questRepository.findAllById(ids);
        Map<Long, QuestVO> rets = new HashMap<>();

        HashSet<Long> uids = new HashSet<>();

        list.forEach(qu -> {
            rets.put(qu.getId(), BeanMapUtils.copy(qu));
            uids.add(qu.getAuthorId());
        });

        // 加载用户信息
        buildUsers(rets.values(), uids);
        return rets;
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public long post(QuestVO quest) {
        Quest qu = new Quest();

        BeanUtils.copyProperties(quest, qu);

        qu.setCreated(new Date());
        qu.setStatus(quest.getStatus());

        // 处理摘要
        if (StringUtils.isBlank(quest.getSummary())) {
            qu.setSummary(trimSummary(quest.getEditor(), quest.getContent()));
        } else {
            qu.setSummary(quest.getSummary());
        }

        questRepository.save(qu);

        String key = ResourceLock.getQuestKey(qu.getId());
        AtomicInteger lock = ResourceLock.getAtomicInteger(key);
        try {
            synchronized (lock){
                QuestAttribute attr = new QuestAttribute();
                attr.setContent(quest.getContent());
                attr.setEditor(quest.getEditor());
                attr.setId(qu.getId());
                questAttributeRepository.save(attr);

                countResource(qu.getId(), null,  attr.getContent());
                onPushEvent(qu, PostUpdateEvent.ACTION_PUBLISH);
                return qu.getId();
            }
        }finally {
            ResourceLock.giveUpAtomicInteger(key);
        }
    }

    private void onPushEvent(Quest quest, int action) {
        PostUpdateEvent event = new PostUpdateEvent(System.currentTimeMillis());
        event.setPostId(quest.getId());
        event.setUserId(quest.getAuthorId());
        event.setAction(action);
        applicationContext.publishEvent(event);
    }

    private void countResource(Long questId, String originContent, String newContent){
        if (StringUtils.isEmpty(originContent)){
            originContent = "";
        }
        if (StringUtils.isEmpty(newContent)){
            newContent = "";
        }

        Set<String> exists = extractImageMd5(originContent);
        Set<String> news = extractImageMd5(newContent);

        List<String> adds = ListUtils.removeAll(news, exists);
        List<String> deleteds = ListUtils.removeAll(exists, news);

        if (adds.size() > 0) {
            List<Resource> resources = resourceRepository.findByMd5In(adds);

            List<QuestResource> qrs = resources.stream().map(n -> {
                QuestResource qr = new QuestResource();
                qr.setResourceId(n.getId());
                qr.setQuestId(questId);
                qr.setPath(n.getPath());
                return qr;
            }).collect(Collectors.toList());
            questResourceRepository.saveAll(qrs);

            resourceRepository.updateAmount(adds, 1);
        }

        if (deleteds.size() > 0) {
            List<Resource> resources = resourceRepository.findByMd5In(deleteds);
            List<Long> rids = resources.stream().map(Resource::getId).collect(Collectors.toList());
            questResourceRepository.deleteByQuestIdAndResourceIdIn(questId, rids);
            resourceRepository.updateAmount(deleteds, -1);
        }
    }

    private Set<String> extractImageMd5(String text) {
//		Pattern pattern = Pattern.compile("(?<=/_signature/)[^/]+?jpg");

        Set<String> md5s = new HashSet<>();

        Matcher originMatcher = pattern.matcher(text);
        while (originMatcher.find()) {
            String key = originMatcher.group();
//			md5s.add(key.substring(0, key.lastIndexOf(".")));
            md5s.add(key);
        }

        return md5s;
    }

    /**
     * 截取问题内容
     * @param text
     * @return
     */
    private String trimSummary(String editor, final String text){
        if (Consts.EDITOR_MARKDOWN.endsWith(editor)) {
            return PreviewTextUtils.getText(MarkdownUtils.renderMarkdown(text), 42);
        } else {
            String aa = PreviewTextUtils.getText(text, 42);
            return aa;
        }
    }

    @Override
    public QuestVO get(long id) {
        Optional<Quest> qu = questRepository.findById(id);
        if (qu.isPresent()) {
            QuestVO d = BeanMapUtils.copy(qu.get());

            d.setAuthor(userService.get(d.getAuthorId()));

            QuestAttribute attr = questAttributeRepository.findById(d.getId()).get();
            d.setContent(attr.getContent());
            d.setEditor(attr.getEditor());
            return d;
        }
        return null;
    }

    /**
     * 更新问题方法
     * @param q
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void update(QuestVO q) {
        Optional<Quest> optional = questRepository.findById(q.getId());

        if (optional.isPresent()) {
            String key = ResourceLock.getQuestKey(q.getId());
            AtomicInteger lock = ResourceLock.getAtomicInteger(key);
            try {
                synchronized (lock){
                    Quest qu = optional.get();
                    qu.setTitle(q.getTitle());//标题
                    qu.setStatus(q.getStatus());

                    // 处理摘要
                    qu.setSummary(trimSummary(q.getEditor(), q.getContent()));


                    // 保存扩展
                    Optional<QuestAttribute> attributeOptional = questAttributeRepository.findById(qu.getId());
                    String originContent = "";
                    if (attributeOptional.isPresent()){
                        originContent = attributeOptional.get().getContent();
                    }
                    QuestAttribute attr = new QuestAttribute();
                    attr.setContent(q.getContent());
                    attr.setEditor(q.getEditor());
                    attr.setId(qu.getId());
                    questAttributeRepository.save(attr);

                    countResource(qu.getId(), originContent, q.getContent());
                }
            }finally {
                ResourceLock.giveUpAtomicInteger(key);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void updateFeatured(long id, int featured) {
        Quest qu = questRepository.findById(id).get();
        int status = Consts.FEATURED_ACTIVE == featured ? Consts.FEATURED_ACTIVE: Consts.FEATURED_DEFAULT;
        qu.setFeatured(status);
        questRepository.save(qu);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void updateWeight(long id, int weighted) {
        Quest qu = questRepository.findById(id).get();

        int max = Consts.ZERO;
        if (Consts.FEATURED_ACTIVE == weighted) {
            max = questRepository.maxWeight() + 1;
        }
        qu.setWeight(max);
        questRepository.save(qu);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void delete(long id, long authorId) {
        Quest qu = questRepository.findById(id).get();
        // 判断文章是否属于当前登录用户
        Assert.isTrue(qu.getAuthorId() == authorId, "认证失败");

        String key = ResourceLock.getQuestKey(qu.getId());
        AtomicInteger lock = ResourceLock.getAtomicInteger(key);
        try	{
            synchronized (lock){
                questRepository.deleteById(id);
                questAttributeRepository.deleteById(id);
                answerService.deleteByQuest(id);
                cleanResource(qu.getId());
                onPushEvent(qu, PostUpdateEvent.ACTION_DELETE);
            }
        }finally {
            ResourceLock.giveUpAtomicInteger(key);
        }
    }

    private void cleanResource(long questId) {
        List<QuestResource> list = questResourceRepository.findByQuestId(questId);
        if (null == list || list.isEmpty()) {
            return;
        }
        List<Long> rids = list.stream().map(QuestResource::getResourceId).collect(Collectors.toList());
        resourceRepository.updateAmountByIds(rids, -1);
        questResourceRepository.deleteByQuestId(questId);
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void delete(Collection<Long> ids) {
        if (CollectionUtils.isNotEmpty(ids)) {
            List<Quest> list = questRepository.findAllById(ids);
            list.forEach(qu -> {
                String key = ResourceLock.getQuestKey(qu.getId());
                AtomicInteger lock = ResourceLock.getAtomicInteger(key);
                try	{
                    synchronized (lock){
                        questRepository.delete(qu);
                        questAttributeRepository.deleteById(qu.getId());
                        answerService.deleteByQuest(qu.getId());
                        cleanResource(qu.getId());
                        onPushEvent(qu, PostUpdateEvent.ACTION_DELETE);
                    }
                }finally {
                    ResourceLock.giveUpAtomicInteger(key);
                }
            });
        }
    }

    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void identityViews(long id) {
        // 不清理缓存, 等待文章缓存自动过期
        questRepository.updateViews(id, Consts.IDENTITY_STEP);
    }

    @Override
    @Transactional
    public void identityAnswers(long id) {
        questRepository.updateAnswers(id, Consts.IDENTITY_STEP);
    }

    @Override
    @PostStatusFilter
    public long count() {
        return questRepository.count();
    }

    @Override
    public List<Quest> findAll(int status) {
        Sort sort = Sort.by(Sort.Direction.DESC, "weight", "id");
        List<Quest> list;
        if (status > Consts.IGNORE) {
            list = questRepository.findAllByStatus(status, sort);
        } else {
            list = questRepository.findAll(sort);
        }
        return list;
    }

    @Override
    public Quest getById(long id) {
        return questRepository.findById(id).get();
    }
}
